#ifndef SL_PLANE_H_
#define SL_PLANE_H_
#include "Config.h"
#include "Matrix4.h"
#include "Vector3.h"
#include "BoundingBox.h"
#include "BoundingSphere.h"
namespace sl
{
	// define the plane in the 3D space 
	// the equation is:
	// a*x + b*y + c*z + d = 0
	typedef float value_type;
	class SL_EXPORT Plane
	{

	public:
		enum Side
		{
			SIDE_NO = 0,
			SIDE_BOTH,
			SIDE_NEGATIVE,
			SIDE_POSITIVE,
			
		};
		Plane(void);
		Plane(value_type fv0, value_type fv1, value_type fv2, value_type fv3 );
		~Plane(void);

		void Set( value_type fv0, value_type fv1, value_type fv2, value_type fv3 )
		{
			m_fv[0] = fv0;
			m_fv[1] = fv1;
			m_fv[2] = fv2;
			m_fv[3] = fv3;
		}

		void MakeNormalise()
		{
			value_type lengh = 1 / sqrt( m_fv[0] * m_fv[0] + m_fv[1] * m_fv[1] + m_fv[2] * m_fv[2]);
			m_fv[0] *= lengh;
			m_fv[1] *= lengh;
			m_fv[2] *= lengh;
			m_fv[3] *= lengh;
		}

		inline float  DistanseToPoint( const Vector3 & vec ) const 
		{
			return m_fv[0] * vec.x() +
				   m_fv[1] * vec.y() +
				   m_fv[2] * vec.z() +
				   m_fv[3];
		}

		inline float DotProductNormal( const Vector3 & vec ) const
		{
			return	m_fv[0] * vec.x() +
					m_fv[1] * vec.y() +
					m_fv[2] * vec.z(); 
		}

		inline float absDotProductNormal( const Vector3 & vec ) const
		{
			return	abs(m_fv[0] * vec.x()) +
					abs(m_fv[1] * vec.y()) +
					abs(m_fv[2] * vec.z());
		}

		Plane::Side	GetSide( const Vector3 & vec )
		{
			float dist = DistanseToPoint( vec );
			if ( dist < 0 )
			{
				return SIDE_NEGATIVE;
			}
			if ( dist > 0 )
			{
				return SIDE_POSITIVE;
			}	

			return SIDE_NO;
		}

		Plane::Side GetSide( BoundingBox & box )
		{
			if( box.IsInifinite() )
				return SIDE_BOTH;
			if ( box.IsNull() )
				return SIDE_NO;
			
			float fMax = absDotProductNormal( box.GetSize() * 0.5 );
			float dist = DistanseToPoint( box.GetCenter() );
			if ( dist < -fMax )
				return SIDE_NEGATIVE;
			if ( dist > fMax )
				return SIDE_POSITIVE;
			return SIDE_NO;
		}

		Plane::Side GetSide( BoundingSphere  & sphere )
		{
			float dist = DistanseToPoint( sphere.GetCenter() );
			float radius = sphere.GetRadius();
			if ( dist < -radius )
				return SIDE_NEGATIVE;
			if ( dist > radius )
				return SIDE_POSITIVE;
			return SIDE_NO;
		}
		
		inline value_type & operator [] (u32 nPos) { assert( nPos > 0 && nPos < 4 ); return m_fv[nPos]; }
	private:
		value_type m_fv[4];
	};
}

#endif

